/*  
 * Actipro Software's WPF DataGrid / Editors Integration Add-on
 * http://actipro.codeplex.com/
 * 
 * Copyright (c) 2009-2015 Actipro Software LLC. 
 *  
 * This source code is subject to the terms of the Microsoft Public License (Ms-PL). 
 *  
 * Redistribution and use in source and binary forms, with or without modification, 
 * is permitted provided that redistributions of the source code retain the above 
 * copyright notices and this file header. 
 *  
 * Additional copyright notices should be appended to the list above. 
 * 
 * For details, see <http://www.opensource.org/licenses/ms-pl.html>. 
 *  
 * All other rights reserved. 
 *
 */  


using System;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using ActiproSoftware.Windows.Controls.Editors.Interop.DataGrid.Primitives;

namespace ActiproSoftware.Windows.Controls.Editors.Interop.DataGrid {

	/// <summary>
	/// Represents a data-bound column for use in a <c>DataGrid</c> that utilizes the <see cref="MaskedTextBox"/>.
	/// </summary>
	public class DataGridMaskedTextColumn : DataGridBoundColumnBase {

		private static Style defaultElementStyle;
		private static Style defaultEditingElementStyle;

		#region Dependency Properties

		/// <summary>
		/// Identifies the <see cref="IsCaseAutoCorrected"/> dependency property.  This field is read-only.
		/// </summary>
		/// <value>The identifier for the <see cref="IsCaseAutoCorrected"/> dependency property.</value>
		// 9/19/2011 - Exposed IsCaseAutoCorrected property (http://www.actiprosoftware.com/support/forums/viewforumtopic.aspx?ForumTopicID=4390)
		public static readonly DependencyProperty IsCaseAutoCorrectedProperty = MaskedTextBox.IsCaseAutoCorrectedProperty.AddOwner(typeof(DataGridMaskedTextColumn),
			new FrameworkPropertyMetadata(MaskedTextBox.IsCaseAutoCorrectedProperty.DefaultMetadata.DefaultValue, OnNotifyablePropertyValueChanged));

		/// <summary>
		/// Identifies the <see cref="IsCaseSensitive"/> dependency property.  This field is read-only.
		/// </summary>
		/// <value>The identifier for the <see cref="IsCaseSensitive"/> dependency property.</value>
		// 9/19/2011 - Exposed IsCaseSensitive property (http://www.actiprosoftware.com/support/forums/viewforumtopic.aspx?ForumTopicID=4390)
		public static readonly DependencyProperty IsCaseSensitiveProperty = MaskedTextBox.IsCaseSensitiveProperty.AddOwner(typeof(DataGridMaskedTextColumn),
			new FrameworkPropertyMetadata(MaskedTextBox.IsCaseSensitiveProperty.DefaultMetadata.DefaultValue, OnNotifyablePropertyValueChanged));

		/// <summary>
		/// Identifies the <see cref="IsMatchRequired"/> dependency property.  This field is read-only.
		/// </summary>
		/// <value>The identifier for the <see cref="IsMatchRequired"/> dependency property.</value>
		public static readonly DependencyProperty IsMatchRequiredProperty = DependencyProperty.Register("IsMatchRequired",
			typeof(bool), typeof(DataGridMaskedTextColumn), new FrameworkPropertyMetadata(false));

		/// <summary>
		/// Identifies the <see cref="Mask"/> dependency property.  This field is read-only.
		/// </summary>
		/// <value>The identifier for the <see cref="Mask"/> dependency property.</value>
		public static readonly DependencyProperty MaskProperty = MaskedTextBox.MaskProperty.AddOwner(typeof(DataGridMaskedTextColumn),
			new FrameworkPropertyMetadata(MaskedTextBox.MaskProperty.DefaultMetadata.DefaultValue, OnNotifyablePropertyValueChanged));

		#endregion // Dependency Properties

		/////////////////////////////////////////////////////////////////////////////////////////////////////
		#region OBJECT
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		/// <summary>
		/// Initializes the <see cref="DataGridMaskedTextColumn"/> class.
		/// </summary>
		[SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
		static DataGridMaskedTextColumn() {
			DataGridBoundColumn.EditingElementStyleProperty.OverrideMetadata(typeof(DataGridMaskedTextColumn),
				new FrameworkPropertyMetadata(DefaultEditingElementStyle));
			DataGridBoundColumn.ElementStyleProperty.OverrideMetadata(typeof(DataGridMaskedTextColumn),
				new FrameworkPropertyMetadata(DefaultElementStyle));
		}

		#endregion // OBJECT

		/////////////////////////////////////////////////////////////////////////////////////////////////////
		#region PUBLIC PROCEDURES
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		/// <summary>
		/// Applies standard values to the specified target element.
		/// </summary>
		/// <param name="targetElement">The target element.</param>
		protected override void ApplyStandardValues(FrameworkElement targetElement) {
			if (targetElement is MaskedTextBox) {
				this.ApplyValue(IsCaseAutoCorrectedProperty, targetElement, MaskedTextBox.IsCaseAutoCorrectedProperty);
				this.ApplyValue(IsCaseSensitiveProperty, targetElement, MaskedTextBox.IsCaseSensitiveProperty);
				this.ApplyValue(MaskProperty, targetElement, MaskedTextBox.MaskProperty);
			}
			base.ApplyStandardValues(targetElement);
		}

		/// <summary>
		/// Gets or sets a value indicating whether a match is required for any changes to be committed.
		/// This is a dependency property.
		/// </summary>
		/// <value>
		/// <c>true</c> if a match is required for any changes to be committed; otherwise <c>false</c>.
		/// The default value is <c>false</c>.
		/// </value>
		public bool IsMatchRequired {
			get { return (bool)this.GetValue(DataGridMaskedTextColumn.IsMatchRequiredProperty); }
			set { this.SetValue(DataGridMaskedTextColumn.IsMatchRequiredProperty, value); }
		}

		/// <summary>
		/// Cancels the cell edit.
		/// </summary>
		/// <param name="editingElement">The editing element.</param>
		/// <param name="uneditedValue">The unedited value.</param>
		protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue) {
			UpdateBindingTarget(editingElement as MaskedTextBox, MaskedTextBox.TextProperty);
		}

		/// <summary>
		/// Commits the cell edit.
		/// </summary>
		/// <param name="editingElement">The editing element.</param>
		/// <returns></returns>
		protected override bool CommitCellEdit(FrameworkElement editingElement) {
			MaskedTextBox maskedTextBox = editingElement as MaskedTextBox;
			if (null != maskedTextBox) {
				if (!this.IsMatchRequired || maskedTextBox.IsMatched)
					UpdateBindingSource(maskedTextBox, MaskedTextBox.TextProperty);
				else
					UpdateBindingTarget(maskedTextBox, MaskedTextBox.TextProperty);
				return !Validation.GetHasError(maskedTextBox);
			}
			return true;
		}

		/// <summary>
		/// Gets the default value for the <c>EditingElementStyleProperty</c> dependency property.
		/// </summary>
		/// <value>The default value for the <c>EditingElementStyleProperty</c> dependency property.</value>
		public static Style DefaultEditingElementStyle {
			get {
				if (defaultEditingElementStyle == null) {
					defaultEditingElementStyle = GenerateBaseStyle(typeof(MaskedTextBox), true);
					defaultEditingElementStyle.Seal();
				}
				return defaultEditingElementStyle;
			}
		}

		/// <summary>
		/// Gets the default value for the <c>EditingElementStyleProperty</c> dependency property.
		/// </summary>
		/// <value>The default value for the <c>EditingElementStyleProperty</c> dependency property.</value>
		public static Style DefaultElementStyle {
			get {
				if (defaultElementStyle == null) {
					defaultElementStyle = GenerateBaseStyle(typeof(MaskedTextBox), false);
					defaultElementStyle.Seal();
				}
				return defaultElementStyle;
			}
		}

		/// <summary>
		/// Generates a <see cref="Style"/> that can be used as the basis for the element styles.
		/// </summary>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="isEditing">if set to <c>true</c> the style will be used for the editing element.</param>
		/// <returns>A <see cref="Style"/> that can be used as the basis for the element styles.</returns>
		protected static Style GenerateBaseStyle(Type targetType, bool isEditing) {
			Style style = new Style(targetType);
			style.Setters.Add(new Setter(MaskedTextBox.BorderThicknessProperty, new Thickness(0.0)));
			style.Setters.Add(new Setter(MaskedTextBox.PaddingProperty, new Thickness(0.0)));
			style.Setters.Add(new Setter(MaskedTextBox.MarginProperty, new Thickness(0.0)));
			style.Setters.Add(new Setter(MaskedTextBox.ForegroundProperty,
				new Binding("Foreground") {
					RelativeSource = new RelativeSource() {
						AncestorType = typeof(DataGridCell)
					}
				}));

			if (!isEditing) {
				style.Setters.Add(new Setter(MaskedTextBox.BackgroundProperty, Brushes.Transparent));
				style.Setters.Add(new Setter(MaskedTextBox.IsHitTestVisibleProperty, false));
				style.Setters.Add(new Setter(MaskedTextBox.FocusableProperty, false));
			}

			return style;
		}

		/// <summary>
		/// Generates an instance of a <see cref="MaskedTextBox"/>.
		/// </summary>
		/// <param name="isEditing">if set to <c>true</c> the edit box will be used for editing.</param>
		/// <param name="cell">The cell.</param>
		/// <returns>An instance of a <see cref="MaskedTextBox"/>.</returns>
		protected virtual MaskedTextBox GenerateMaskedTextBox(bool isEditing, DataGridCell cell) {
			MaskedTextBox editBox = null;
			if (null != cell) {
				if (typeof(MaskedTextBox).IsInstanceOfType(cell.Content))
					editBox = (MaskedTextBox)cell.Content;
			}

			if (null == editBox)
				editBox = (MaskedTextBox)Activator.CreateInstance(typeof(MaskedTextBox));

			this.ApplyStandardValues(editBox);
			if (isEditing)
				editBox.Style = this.EditingElementStyle;
			else
				editBox.Style = this.ElementStyle;
			this.ApplyBinding(editBox, MaskedTextBox.TextProperty);
			return editBox;
		}

		/// <summary>
		/// Generates the editing element.
		/// </summary>
		/// <param name="cell">The cell.</param>
		/// <param name="dataItem">The data item.</param>
		/// <returns>The <c>FrameworkElement</c> to use for editing.</returns>
		protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
			return this.GenerateMaskedTextBox(true, cell);
		}

		/// <summary>
		/// Generates the element.
		/// </summary>
		/// <param name="cell">The cell.</param>
		/// <param name="dataItem">The data item.</param>
		/// <returns>
		/// The <c>FrameworkElement</c> to use for display.
		/// </returns>
		protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
			return this.GenerateMaskedTextBox(false, cell);
		}

		/// <summary>
		/// Gets or sets a value indicating whether characters entered will have their case auto-corrected to match the defined mask.
		/// This is a dependency property.
		/// </summary>
		/// <value>
		/// <c>true</c> if characters entered will have their case auto-corrected to match the defined mask; otherwise, <c>false</c>.
		/// The default value is <c>false</c>.
		/// </value>
		/// <remarks>
		/// When this property is set to <c>true</c>, then the <see cref="IsCaseSensitive"/> property is ignored.
		/// </remarks>
		public bool IsCaseAutoCorrected {
			get { return (bool)this.GetValue(DataGridMaskedTextColumn.IsCaseAutoCorrectedProperty); }
			set { this.SetValue(DataGridMaskedTextColumn.IsCaseAutoCorrectedProperty, value); }
		}

		/// <summary>
		/// Gets or sets a value indicating whether the mask is case sensitive.
		/// This is a dependency property.
		/// </summary>
		/// <value>
		/// <c>true</c> if the mask is case sensitive; otherwise, <c>false</c>.
		/// The default value is <c>false</c>.
		/// </value>
		/// <remarks>
		/// When the <see cref="IsCaseAutoCorrected"/> property is set to <c>true</c>, then this property is ignored.
		/// </remarks>
		public bool IsCaseSensitive {
			get { return (bool)this.GetValue(DataGridMaskedTextColumn.IsCaseSensitiveProperty); }
			set { this.SetValue(DataGridMaskedTextColumn.IsCaseSensitiveProperty, value); }
		}

		/// <summary>
		/// Gets or sets the input mask.
		/// This is a dependency property.
		/// </summary>
		/// <value>
		/// The input mask.
		/// The default value is <see langword="null"/>.
		/// </value>
		public string Mask {
			get { return (string)this.GetValue(DataGridMaskedTextColumn.MaskProperty); }
			set { this.SetValue(DataGridMaskedTextColumn.MaskProperty, value); }
		}

		/// <summary>
		/// Prepares the cell for edit.
		/// </summary>
		/// <param name="editingElement">The editing element.</param>
		/// <param name="editingEventArgs">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
		/// <returns></returns>
		protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs) {
			MaskedTextBox maskedTextBox = editingElement as MaskedTextBox;
			if (null == maskedTextBox)
				return null;

			maskedTextBox.Focus();
			return maskedTextBox.Text;
		}

		#endregion // PUBLIC PROCEDURES
	}
}
